﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Amazon.DynamoDB;
using Amazon.DynamoDB.DataModel;
using Amazon.DynamoDB.DocumentModel;
using Amazon.DynamoDB.Model;
using Amazon.SecurityToken;
using Amazon.Runtime;
using DevToolBox.Cloud.Common;

namespace DevToolBox.Cloud.Amazon
{
    /// <summary>
    /// Class to encapsulate the nteraction with the Amazon DynamoDb
    /// Entity Framework provider
    /// </summary>
    public class DynamoDbEF : DynamoDbClient, IEFClient 
    {
        DynamoDBContext context = null;
        private int MAX_DYNAMO_WRITE_BATCH= 25;

        /// <summary>
        /// Default constructor
        /// </summary>
        public DynamoDbEF():base()
        {
            context = new DynamoDBContext(Client);
        }

        /// <summary>
        /// Method to save an list of T objects in the DynamoDb Service
        /// </summary>
        /// <typeparam name="T">Data type of the entities</typeparam>
        /// <param name="entities">List of entities to save</param>
        public void Save<T>(List<T> list)
        {
            List<T> entities = null;
            if (list != null)
            {
                entities = list.ToList();
            }

            if (entities.Count > 0)
            {
                if (entities.Count == 1)
                {
                    context.Save<T>(entities[0]);
                }
                else
                {
                    int counter = 0;
                    List<T> groupOfItemsToSave = new List<T>();

                    while (entities.Count > 0)
                    {
                        groupOfItemsToSave.Add(entities[0]);
                        entities.RemoveAt(0);
                        counter++;

                        if (counter == MAX_DYNAMO_WRITE_BATCH || entities.Count == 0)
                        {
                            BatchWrite<T> request = context.CreateBatchWrite<T>();
                            request.AddPutItems(groupOfItemsToSave);
                            context.ExecuteBatchWrite(new BatchWrite[] { request });
                            groupOfItemsToSave.Clear();
                            counter = 0;
                        }
                    }
                }
            }

            //Batch writes replaced single writes
            /*
            foreach (T entity in entities)
            {
                context.Save<T>(entity);
            }
            */
        }

        /// <summary>
        /// Method to query an Amazon DynamoDb table based on
        /// a single primary/main key
        /// </summary>
        /// <typeparam name="T">Return object data type</typeparam>
        /// <param name="key">Primary/main key</param>
        /// <returns>IEnumerable set of T objects</returns>
        /// <remarks>
        /// It does not retry a failed request. See: http://bit.ly/11iTV4m 
        /// </remarks>
        public IEnumerable<T> Query<T>(object key)
        {
            return context.Query<T>(key); 
        }

        /// <summary>
        /// Method to query an Amazon DynamoDb table based on
        /// a single primary/main key plus a set of conditions
        /// </summary>
        /// <typeparam name="T">Return object data type</typeparam>
        /// <param name="key">Primary/main key</param>
        /// <param name="conditionOperator">Any of these operators: =, BW, GT, GE</param>
        /// <param name="values">Values to be evaluated using the condition</param>
        /// <returns>IEnumerable set of T objects</returns>
        public IEnumerable<T> Query<T>(object key, string conditionOperator, object[] values)
        {
            QueryOperator oper = QueryOperator.Equal;
            switch (conditionOperator)
            {
                case "=":
                    oper = QueryOperator.Equal;
                    break;
                case "BW":
                    oper = QueryOperator.BeginsWith;
                    break;
                case "GT":
                    oper = QueryOperator.GreaterThan;
                    break;
                case "GE":
                    oper = QueryOperator.GreaterThanOrEqual;
                    break;
            }

            return context.Query<T>(key, oper, values);
        }

        /// <summary>
        /// Scans an Amazon DynamoDb table based on
        /// a single primary/main key plus a set of conditions
        /// </summary>
        /// <typeparam name="T">Return object data type</typeparam>
        /// <param name="conditions">Set of conditions</param>
        /// <returns>IEnumerable set of T objects</returns>
        public IEnumerable<T> Scan<T>(CommonCondition[] conditions)
        {
            List<ScanCondition> conds = new List<ScanCondition>();

            foreach (CommonCondition cond in conditions)
            {
                ScanOperator oper = ScanOperator.IsNotNull;
                switch (cond.Operator)
                {
                    case "=":
                        oper = ScanOperator.Equal;
                        break;
                    case "BW":
                        oper = ScanOperator.BeginsWith;
                        break;
                    case "GE":
                        oper = ScanOperator.GreaterThanOrEqual;
                        break;
                }
                ScanCondition newCond = new ScanCondition(cond.AttributeName, oper, cond.Value);
                conds.Add(newCond);
            }

            return context.Scan<T>(conds.ToArray());
        }

        /// <summary>
        /// Frees the unmanaged resources
        /// </summary>
        public void Dispose()
        {
            if (Client != null)
            {
                Client.Dispose();
            }
        }

        /// <summary>
        /// Function to delete a list of ENtities
        /// </summary>
        /// <typeparam name="T">Specific data type</typeparam>
        /// <param name="hashKeys">List of keys</param>
        public void Delete<T>(List<T> hashKeys)
        {
            foreach (T entity in hashKeys)
            {
                context.Delete<T>(entity);
            }
        }
    }
}
